package com.jellybean.augmentedoverlay;

import java.util.List;

import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.OverlayItem;
import com.jellybean.augmentedreality.R;

/**
 * An abstract extension of ItemizedOverlay for displaying an information balloon
 * upon screen-tap of each marker overlay.
 * 
 * @author Jeff Gilfelt
 */
public class AugmentedRealityOverlay<Item extends OverlayItem> extends ItemizedOverlay<Item> {

	private static final long BALLOON_INFLATION_TIME = 300;
	private static Handler handler = new Handler();

	private MapView mapView;
	private AugmentedRealityView<Item> augmentedView;
	private View clickRegion;
	private View closeRegion;
	private int viewOffset;
	final MapController mc;
	private Item currentFocusedItem;
	private int currentFocusedIndex;

	private boolean showClose = true;
	private boolean showDisclosure = false;
	private boolean snapToCenter = true;

	private static boolean isInflating = false;

	/**
	 * Create a new AugmentationItemizedOverlay
	 * 
	 * @param defaultMarker - A bounded Drawable to be drawn on the map for each item in the overlay.
	 * @param mapView - The view upon which the overlay items are to be drawn.
	 */
	public AugmentedRealityOverlay(Drawable defaultMarker, MapView mapView) {
		super(defaultMarker);
		this.mapView = mapView;
		viewOffset = 0;
		mc = mapView.getController();
	}

	/**
	 * Set the horizontal distance between the marker and the bottom of the information
	 * balloon. The default is 0 which works well for center bounded markers. If your
	 * marker is center-bottom bounded, call this before adding overlay items to ensure
	 * the balloon hovers exactly above the marker. 
	 * 
	 * @param pixels - The padding between the center point and the bottom of the
	 * information balloon.
	 */
	public void setAugmentationBottomOffset(int pixels) {
		viewOffset = pixels;
	}
	public int getAugmentationBottomOffset() {
		return viewOffset;
	}

	/**
	 * Override this method to handle a "tap" on a balloon. By default, does nothing 
	 * and returns false.
	 * 
	 * @param index - The index of the item whose balloon is tapped.
	 * @param item - The item whose balloon is tapped.
	 * @return true if you handled the tap, otherwise false.
	 */
	protected boolean onAugmentationTap(int index, Item item) {
		return false;
	}

	/**
	 * Override this method to perform actions upon an item being tapped before 
	 * its balloon is displayed.
	 * 
	 * @param index - The index of the item tapped.
	 */
	protected void onAugmentationOpen(int index) {}

	/* (non-Javadoc)
	 * @see com.google.android.maps.ItemizedOverlay#onTap(int)
	 */
	@Override
	//protected final boolean onTap(int index) {
	public final boolean onTap(int index) {

		handler.removeCallbacks(finishAugmentationInflation);
		isInflating = true;
		handler.postDelayed(finishAugmentationInflation, BALLOON_INFLATION_TIME);

		currentFocusedIndex = index;
		currentFocusedItem = createItem(index);
		setLastFocusedIndex(index);

		onAugmentationOpen(index);
		createAndDisplayAugmentedReality();

		if (snapToCenter) {
			animateTo(index, currentFocusedItem.getPoint());
		}

		return true;
	}

	/**
	 * Animates to the given center point. Override to customize how the
	 * MapView is animated to the given center point
	 *
	 * @param index The index of the item to center
	 * @param center The center point of the item
	 */
	protected void animateTo(int index, GeoPoint center) {
		mc.animateTo(center);
	}

	/**
	 * Creates the balloon view. Override to create a sub-classed view that
	 * can populate additional sub-views.
	 */
	protected AugmentedRealityView<Item> createAugmentedRealityView() {
		return new AugmentedRealityView<Item>(getMapView().getContext(), getAugmentationBottomOffset());
	}

	/**
	 * Expose map view to subclasses.
	 * Helps with creation of balloon views. 
	 */
	protected MapView getMapView() {
		return mapView;
	}

	/**
	 * Makes the balloon the topmost item by calling View.bringToFront().
	 */
	public void bringAugmentationToFront() {
		if (augmentedView != null) {
			augmentedView.bringToFront();
		}
	}

	/**
	 * Sets the visibility of this overlay's balloon view to GONE and unfocus the item. 
	 */
	public void hideAugmentation() {
		if (augmentedView != null) {
			augmentedView.setVisibility(View.GONE);
		}
		currentFocusedItem = null;
	}

	/**
	 * Hides the balloon view for any other AugmentationItemizedOverlay instances
	 * that might be present on the MapView.
	 * 
	 * @param overlays - list of overlays (including this) on the MapView.
	 */
	private void hideOtherAugmentations(List<Overlay> overlays) {

		for (Overlay overlay : overlays) {
			if (overlay instanceof AugmentedRealityOverlay<?> && overlay != this) {
				((AugmentedRealityOverlay<?>) overlay).hideAugmentation();
			}
		}

	}

	public void hideAllAugmentations() {
		if (!isInflating) {
			List<Overlay> mapOverlays = mapView.getOverlays();
			if (mapOverlays.size() > 1) {
				hideOtherAugmentations(mapOverlays);
			}
			hideAugmentation();
		}
	}

	/**
	 * Sets the onTouchListener for the balloon being displayed, calling the
	 * overridden {@link #onAugmentationTap} method.
	 */
	private OnTouchListener createAugmentationTouchListener() {
		return new OnTouchListener() {

			float startX;
			float startY;

			public boolean onTouch(View v, MotionEvent event) {

				View l =  ((View) v.getParent()).findViewById(R.id.reality_main_layout);
				Drawable d = l.getBackground();

				if (event.getAction() == MotionEvent.ACTION_DOWN) {
					if (d != null) {
						int[] states = {android.R.attr.state_pressed};
						if (d.setState(states)) {
							d.invalidateSelf();
						}
					}
					startX = event.getX();
					startY = event.getY();
					return true;
				} else if (event.getAction() == MotionEvent.ACTION_UP) {
					if (d != null) {
						int newStates[] = {};
						if (d.setState(newStates)) {
							d.invalidateSelf();
						}
					}
					if (Math.abs(startX - event.getX()) < 40 && 
							Math.abs(startY - event.getY()) < 40 ) {
						// call overridden method
						onAugmentationTap(currentFocusedIndex, currentFocusedItem);
					}
					return true;
				} else {
					return false;
				}

			}
		};
	}

	/* (non-Javadoc)
	 * @see com.google.android.maps.ItemizedOverlay#getFocus()
	 */
	@Override
	public Item getFocus() {
		return currentFocusedItem;
	}

	/* (non-Javadoc)
	 * @see com.google.android.maps.ItemizedOverlay#setFocus(Item)
	 */
	@Override
	public void setFocus(Item item) {
		super.setFocus(item);	
		currentFocusedIndex = getLastFocusedIndex();
		currentFocusedItem = item;
		if (currentFocusedItem == null) {
			hideAugmentation();
		} else {
			createAndDisplayAugmentedReality();
		}	
	}

	/**
	 * Creates and displays the balloon overlay by recycling the current 
	 * balloon or by inflating it from xml. 
	 * @return true if the balloon was recycled false otherwise 
	 */
	private boolean createAndDisplayAugmentedReality(){
		boolean isRecycled;
		if (augmentedView == null) {
			augmentedView = createAugmentedRealityView();
			clickRegion = (View) augmentedView.findViewById(R.id.reality_inner_layout);
			clickRegion.setOnTouchListener(createAugmentationTouchListener());
			closeRegion = (View) augmentedView.findViewById(R.id.reality_close);
			if (closeRegion != null) {
				if (!showClose) {
					closeRegion.setVisibility(View.GONE);
				} else {
					closeRegion.setOnClickListener(new OnClickListener() {
						public void onClick(View v) {
							hideAugmentation();	
						}
					});
				}
			}
			if (showDisclosure && !showClose) {
				View v = augmentedView.findViewById(R.id.reality_disclosure);
				if (v != null) {
					v.setVisibility(View.VISIBLE);
				}
			}
			isRecycled = false;
		} else {
			isRecycled = true;
		}

		augmentedView.setVisibility(View.GONE);

		List<Overlay> mapOverlays = mapView.getOverlays();
		if (mapOverlays.size() > 1) {
			hideOtherAugmentations(mapOverlays);
		}

		if (currentFocusedItem != null)
			augmentedView.setData(currentFocusedItem);

		GeoPoint point = currentFocusedItem.getPoint();
		MapView.LayoutParams params = new MapView.LayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, point,
				MapView.LayoutParams.BOTTOM_CENTER);
		params.mode = MapView.LayoutParams.MODE_MAP;

		augmentedView.setVisibility(View.VISIBLE);

		if (isRecycled) {
			augmentedView.setLayoutParams(params);
		} else {
			mapView.addView(augmentedView, params);
		}

		return isRecycled;
	}

	public void setShowClose(boolean showClose) {
		this.showClose = showClose;
	}

	public void setShowDisclosure(boolean showDisclosure) {
		this.showDisclosure = showDisclosure;
	}

	public void setSnapToCenter(boolean snapToCenter) {
		this.snapToCenter = snapToCenter;
	}

	public static boolean isInflating() {
		return isInflating;
	}

	private static Runnable finishAugmentationInflation = new Runnable() {
		public void run() {
			isInflating = false;
		}
	};

	@Override
	protected Item createItem(int arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int size() {
		// TODO Auto-generated method stub
		return 0;
	}

}
